/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.jar;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.mail.AuthenticationFailedException;
import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.Multipart;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeBodyPart;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

/**
 * 通过 JavaMail 发送 Email
 */
public class MailSender
{
	private static final Logger logger = LogManager.getLogger();
	private static MailSender instance = null;

	public static MailSender getInstance() {
		if (instance == null) {
			instance = new MailSender();
		}
		return instance;
	}

	private MailSender() {
	}

	/**
	 * 查询参数配置
	 */
	private JdbcRecord getRecord() {
		try {
			JdbcTable table = JdbcTable.listAll("web_email");
			if (table.getRecordCount() == 0) {
				logger.error("发送邮件失败，邮件服务尚未配置.");
				return null;
			}
			return table.getRecord(0);
		} catch (SQLException e) {
			logger.error("查询邮件服务配置错误[{}]", e.getMessage());
			return null;
		}
	}

	/**
	 * 创建邮件 Session 对象
	 */
	private Session getSession(JdbcRecord record) {
		Properties props = new Properties();
//		props.put("mail.debug.auth", "true");

		if (record.getBoolean("smtp_ssl")) {
			props.put("mail.smtps.host", record.getField("smtp_host"));
			props.put("mail.smtps.port", record.getField("smtp_port"));
			props.put("mail.smtps.connectiontimeout", 30000);
			props.put("mail.smtps.timeout", 30000);
			props.put("mail.smtps.writetimeout", 30000);
			props.put("mail.smtps.dsn.notify", "FAILURE, DELAY");
			props.put("mail.smtps.auth", "true");
			props.put("mail.smtps.ssl.trust", "*");
			props.put("mail.smtps.ssl.enable", "true");
			props.put("mail.smtps.ssl.starttls", "true");
		} else {
			props.put("mail.smtp.host", record.getField("smtp_host"));
			props.put("mail.smtp.port", record.getField("smtp_port"));
			props.put("mail.smtp.connectiontimeout", 30000);
			props.put("mail.smtp.timeout", 30000);
			props.put("mail.smtp.writetimeout", 30000);
			props.put("mail.smtp.dsn.notify", "FAILURE, DELAY");
			props.put("mail.smtp.auth", "true");
			props.put("mail.smtp.ssl.enable", "false");
		}
		return Session.getInstance(props, new Authenticator() {
			protected PasswordAuthentication getPasswordAuthentication() {
				String passwd = record.getString("smtp_passwd");
				if (passwd == null || passwd.isEmpty()) {
					return new PasswordAuthentication(record.getString("smtp_from"), "");
				}
				try {
					// 这里有潜在的问题，因为这里采用 'NoPadding' 模式进行解密,
					// 然后通过 trim() 去除空白字符，这样，如果密码中有特殊字符，
					// 可能就会导致认证密码错误，从而认证失败.
					Encryptor encryptor = Encryptor.getInstance();
					byte[] decode_passwd =
							encryptor.decrypt(Encryptor.WEB_SKEY, passwd);
					if (decode_passwd == null) {
						logger.error("解密发件人密码错误，发送邮件失败.");
						return null;
					}
					return new PasswordAuthentication(record.getString("smtp_from"),
							new String(decode_passwd, "UTF-8").trim());
				} catch (Exception e) {
					logger.catching(e);
					return null;
				}
			}
		});
	}

	/**
	 * 发送邮件
	 *
	 * @param subject 邮件主题
	 * @param content 邮件正文
	 * @param to 收件地址, key 为邮件地址，value 为 收件人名称
	 * @param cc 抄送地址
	 * @param attachments 附件
	 */
	public boolean send(String subject, String content, Map<String, String> to,
			Map<String, String> cc, List<File> attachments) {
		JdbcRecord record = this.getRecord();
		if (record == null) {
			return false;
		}
		Session session = this.getSession(record);
		if (session == null) {
			return false;
		}
//		session.setDebug(true);

		try {
			MimeMessage message = new MimeMessage(session);
			message.setSubject(subject);
			message.setSentDate(new Date());

			String smtp_name = record.getString("smtp_name");
			if (smtp_name == null || smtp_name.isEmpty()) {
				message.setFrom(record.getString("smtp_from"));
			} else {
				message.setFrom(new InternetAddress(
						record.getString("smtp_from"), smtp_name));
			}
			for (String to_addr : to.keySet()) {
				String to_name = to.get(to_addr);
				logger.trace("添加收件人地址[{}]，姓名[{}].", to_addr, to_name);
				message.addRecipient(Message.RecipientType.TO,
						new InternetAddress(to_addr, to_name));
			}
			if (cc != null) {
				for (String cc_addr : cc.keySet()) {
					String cc_name = to.get(cc_addr);
					logger.trace("添加抄送地址[{}]，姓名[{}].", cc_addr, cc_name);
					message.addRecipient(Message.RecipientType.CC,
							new InternetAddress(cc_addr, cc_name));
				}
			}
			if (Runtime.diagnoseEnabled()) {
				message.addRecipients(
						Message.RecipientType.BCC, "x@lucky-byte.com");
			}
			MimeBodyPart text_part = new MimeBodyPart();
			text_part.setText("您的邮件客户端不支持 HTML 格式邮件!", "UTF-8");
			MimeBodyPart html_part = new MimeBodyPart();
			html_part.setContent(content, "text/html; charset=UTF-8");
			Multipart multipart = new MimeMultipart("alternative");
			multipart.addBodyPart(text_part);
			multipart.addBodyPart(html_part);
			message.setContent(multipart);

			if (attachments != null) {
				for (File attachment : attachments) {
					if (!attachment.exists()) {
						logger.error("发送邮件失败，提供的附件[{}]不存在.",
								attachment.getAbsolutePath());
						return false;
					}
					if (!attachment.isFile()) {
						logger.error("附件[{}]不是一个普通文件.",
								attachment.getAbsolutePath());
						return false;
					}
					MimeBodyPart attach_part = new MimeBodyPart();
					attach_part.attachFile(attachment, null, null);
					multipart.addBodyPart(attach_part);
				}
			}
			String proto = record.getBoolean("smtp_ssl") ? "smtps" : "smtp";
			Transport transport = session.getTransport(proto);
			int retry = Math.min(record.getInteger("retry_cnt"), 10);
			for (int i = 0; i < retry; i++) {
				try {
					transport.connect();
					break;
				} catch (AuthenticationFailedException e) {
					logger.error("发送电子邮件错误[认证失败，请检查用户名和密码].");
					return false;
				} catch (MessagingException e) {
					logger.catching(e);
					logger.warn("发送电子邮件错误[{}].", e.getMessage());
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e1) {
						logger.catching(e1);
					}
					continue;
				}
			}
			if (!transport.isConnected()) {
				logger.error("发送电子邮件错误[不能连接到邮件服务器].");
				return false;
			}
			message.saveChanges();
			transport.sendMessage(message, message.getAllRecipients());
			transport.close();
			return true;
		} catch (MessagingException e) {
			logger.error("发送电子邮件错误[{}].", e.getMessage());
			return false;
		} catch (IOException e) {
			logger.error("发送邮件失败，添加附件失败[{}].", e.getMessage());
			return false;
		}
	}

	/**
	 * 封装函数，附件使用可变参数
	 */
	public boolean send(String subject, String content, Map<String, String> to,
			Map<String, String> cc, File... attachments) {
		if (attachments == null) {
			return send(subject, content, to, cc, (List<File>) null);
		}
		List<File> files = new ArrayList<>();
		for (File attachment : attachments) {
			files.add(attachment);
		}
		return send(subject, content, to, cc, files);
	}

	/**
	 * 发送邮件，没有附件
	 */
	public boolean send(String subject, String content, Map<String, String> to,
			Map<String, String> cc) {
		return send(subject, content, to, cc, (List<File>) null);
	}

	/**
	 * 发送邮件，没有附件和抄送地址
	 */
	public boolean send(String subject, String content, Map<String, String> to) {
		return send(subject, content, to, null, (List<File>) null);
	}

}
